package com.google.code.maven.instrument;

/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.File;
import java.lang.instrument.ClassFileTransformer;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;

import com.google.code.maven.instrument.util.ClassFile;
import com.google.code.maven.instrument.util.ClassFileLocator;

/**
 * Goal which performs compile time class instrumentation using Java agents.
 * 
 * Applies one or more java.lang.instrument.ClassFileTransformer agents to the compiled class files to perform bytecode
 * instrumentation.
 * 
 * @goal instrument
 * 
 * @phase process-classes
 * @see {@link java.lang.instrument.ClassFileTransformer}
 * @author Tim Myerscough
 */
public class InstrumentMojo extends AbstractMojo {

	/**
	 * The fully qualified class names of the transformers to apply.
	 * 
	 * @parameter
	 * @required
	 */
	private List<String> classTransformers;

	/**
	 * @parameter default-value=${project.build.outputDirectory}
	 * @read-only
	 * @required
	 */
	private String outputDirectory;

	@Override
	public void execute() throws MojoExecutionException, MojoFailureException {

		final ClassFileLocator locator = createLocator();
		final Collection<ClassFileTransformer> agents = getAgents();
		final File outputDirectoryDir = new File(outputDirectory);
		final Collection<ClassFile> classFiles = locator
				.findClasses(outputDirectoryDir);

		performClassTransformation(classFiles, agents);

	}

	private void performClassTransformation(
			final Collection<ClassFile> classFiles,
			final Collection<ClassFileTransformer> agents)
			throws MojoExecutionException {
		for (final ClassFile classFile : classFiles) {
			for (final ClassFileTransformer agent : agents) {
				transformClass(classFile, agent);
			}
		}
	}

	private void transformClass(final ClassFile classFile,
			final ClassFileTransformer agent) throws MojoExecutionException {
		try {
			classFile.transform(agent);
		} catch (final ClassTransformationException e) {
			final String message = MessageFormat
					.format("Failed to transform class: {0}, using ClassFileTransformer, {1}",
							classFile, agent.getClass());
			throw new MojoExecutionException(message, e);
		}
	}

	private Collection<ClassFileTransformer> getAgents()
			throws MojoExecutionException {
		final Collection<ClassFileTransformer> agents = new ArrayList<ClassFileTransformer>();
		for (final String className : classTransformers) {
			final ClassFileTransformer instance = createAgentInstance(className);
			agents.add(instance);
		}
		return agents;
	}

	private ClassFileTransformer createAgentInstance(final String className)
			throws MojoExecutionException {
		final Class<?> agentClass = resolveClass(className);
		if (!ClassFileTransformer.class.isAssignableFrom(agentClass)) {
			final String message = className + "is not an instance of "
					+ ClassFileTransformer.class;
			throw new MojoExecutionException(message);
		}
		final ClassFileTransformer instance = toClassFileTransformerInstance(agentClass);
		return instance;
	}

	private ClassFileTransformer toClassFileTransformerInstance(
			final Class<?> agentClass) throws MojoExecutionException {
		try {
			return (ClassFileTransformer) agentClass.newInstance();
		} catch (final InstantiationException e) {
			throw new MojoExecutionException("Failed to instantiate class: "
					+ agentClass + ". Does it have a no-arg constructor?", e);
		} catch (final IllegalAccessException e) {
			throw new MojoExecutionException(agentClass
					+ ". Does not have a public no-arg constructor?", e);
		}
	}

	private Class<?> resolveClass(final String className)
			throws MojoExecutionException {
		try {
			return Class.forName(className);
		} catch (final ClassNotFoundException e) {
			final String message = MessageFormat
					.format("Could not find class: {0}. Is it a registered dependency of the project or the plugin?",
							className);
			throw new MojoExecutionException(message, e);
		}
	}

	private ClassFileLocator createLocator() {
		final ClassFileLocator locator = new ClassFileLocator();
		locator.setLog(getLog());
		return locator;
	}
}
